#include "scanner.h"
#include <ctype.h>
#include <cstdio>
lex_type_t scanner::words[]
{
    LEX_AND, //0
    LEX_NOT, //1
    LEX_OR, //2
    LEX_FALSE,//3
    LEX_TRUE,//4
    LEX_BEGIN,//5 //{ bracket
    LEX_BOOL_DECL,//6
    LEX_DO,//7
    LEX_END, //8 // } bracket
    LEX_ELSE,//9
    LEX_IF,//10
    LEX_INT_DECL,//11
    LEX_REAL,//12
    LEX_PROGRAM,//13
    LEX_READ,//14
    LEX_WRITE,//15
    LEX_STRING_DECL,//16
    LEX_FOR,//17
    LEX_WHILE,//18
    LEX_CONTINUE, //19
    LEX_NULL//20

};

string scanner::TW[]
{
    "and",
    "not",
    "or",
    "false",
    "true",
    "{",
    "bool",
    "do",
    "}",
    "else",
    "if",
    "int",
    "real",
    "program",
    "read",
    "write",
    "string",
    "for",
    "while",
    "continue",
    ""
};


lex_type_t scanner::delims[] {
    LEX_FIN,
    LEX_SEMICOLON,
    LEX_COMMA,
    LEX_COLON,
    LEX_ASSIGN,
    LEX_EQ,
    LEX_LESS,
    LEX_GREATER,
    LEX_PLUS,
    LEX_MINUS,
    LEX_TIMES,
    LEX_SLASH,
    LEX_PLUS_ASSIGN,
    LEX_MINUS_ASSIGN,
    LEX_TIMES_ASSIGN,
    LEX_SLASH_ASSIGN,
    LEX_MOD,
    LEX_MOD_ASSIGN,
    LEX_NEQ,
    LEX_GEQ,
    LEX_LEQ,
    LEX_LPAREN,
    LEX_RPAREN,
    LEX_EQ,
    LEX_UNARY_PLUS,
    LEX_UNARY_MINUS,
    LEX_NULL
};

string scanner::TD[] {
    "@",
    ";",
    ",",
    ":",
    "=",
    "==",
    "<",
    ">",
    "+",
    "-",
    "*",
    "/",
    "+=",
    "-=",
    "*=",
    "-=",
    "%",
    "%=",
    "!=",
    ">=",
    "<=",
    "(",
    ")",
    ""
};

scanner::scanner(const string &fname)
{

    TD[0].resize(1);
    TD[0][0] = EOF;
    buff = "";
    fin.open(fname);
    if (!fin.is_open()) {
        throw "Can`t open file";
    }
    lineno = 1;
    gc();
}

static bool xisspace(int c) {
    return (c == ' ')|| (c == '\n') || (c == '\t') || (c == '\r');
}

lex scanner::get_lex()
{
    int numb = 0, ind;
    string *pstr;
    char c2;
    bool minus_flag = false;
    current_state = ST_START;
    do
    {
        switch (current_state)
        {
        case ST_START:
            if (xisspace(c)) {
                gc();
            } else if(isalpha(c) || c == '_' || c == '{' || c == '}') {
                buff.clear();
                buff.push_back(c);
                gc();
                current_state = ST_IDENT;
            } else if (isdigit(c)) {
                numb = c - '0';
                gc();
                current_state = ST_NUMB;
            } else if (c == '+' || c == '-') {
                current_state = ST_EXPR_NUMB;
                c2 = c;
                gc();
            } else if (c == '*' || c == '/' || c == '%') {
                c2  = c;
                gc();
                current_state = ST_PREDELIM;
            } else if (c == '\"') {
                pstr = new string();
                gc();
                current_state = ST_STRING;
            } else if (c == '<' || c == '>' || c == '=') {
                buff.clear();
                buff.push_back(c);
                gc();
                current_state = ST_ALE;
            } else if (c == (-1)) {
                return lex(LEX_FIN);
            } else if (c == '!') {
                buff.clear();
                buff.push_back(c);
                gc();
                current_state = ST_NEQ;
            } else {
                current_state = ST_DELIM;
            }
            break;
        case ST_EXPR_NUMB:
            if (isdigit(c)) {
                if (c2 == '-') {
                    minus_flag = true;
                }
                current_state = ST_NUMB;
            } else {
                current_state = ST_PREDELIM;
            }
            break;

        case ST_PREDELIM:
            if (c != '=') {
                ungc(c);
                c = c2;
                current_state = ST_DELIM;
            } else {
                buff.clear();
                buff.push_back(c2);
                buff.push_back(c);
                ind = TD_find(buff);
                if (ind != -1) {
                    gc();
                    return lex(delims[ind], ind);
                } else {
                    throw get_lineno() + c;
                }
            }
            break;
        case ST_IDENT:
            if (isalnum(c) && buff[0] != '{' && buff[0] != '}') {
                buff.push_back(c);
                gc();
            } else if ((ind = TW_find(buff)) != -1) {
                return lex(words[ind], ind);
            } else {
                ind = m_tid.push(ident(buff, LEX_IDENT));
                return lex(LEX_IDENT, ind);
            }
            break;
        case ST_NUMB:
            if (isdigit(c)) {
                numb = numb * 10 + (c - '0');
                gc();
            } else {
                if (minus_flag)  {
                    numb = -numb;
                }
                return lex(LEX_NUM, numb);
            }
            break;
        case ST_ALE:
            if (c == '=') {
                buff.push_back(c);
                gc();
                ind = TD_find(buff);
                if (ind != -1) {
                    return lex(delims[ind], ind);
                } else {
                    throw get_lineno() + c;
                }
            } else {
                ind = TD_find(buff);
                if (ind != -1) {
                    return lex(delims[ind], ind);
                } else {
                    throw get_lineno() + c;
                }
            }
            break;
        case ST_NEQ:
            if (c != '=') {
                throw get_lineno() + "Invalid operator";
            }
            buff.push_back(c);
            gc();
            ind = TD_find(buff);
            return lex(delims[ind], ind);
            break;
        case ST_DELIM:
            buff.clear();
            buff.push_back(c);
            ind = TD_find(buff);
            if (ind != -1) {
                gc();
                return lex(delims[ind], ind);
            } else {
                throw get_lineno() + c;
            }
            break;
        case ST_STRING:
            if (c == '\"') {
                int id = TS_find(*pstr);
                if (id == -1) {
                    m_TS.push_back(*pstr);
                    id = (int)(m_TS.size() - 1);
                }
                gc();
                return lex(LEX_STRING, id);
            }
            if (c == EOF) {
                throw get_lineno() + "Unterminated string";
            }
            (*pstr) += c;
            gc();
            break;
        default:
            break;
        }
    } while (1);
}
